﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Reflection;
using QbservableCore.Serialization.Helpers;

namespace QbservableCore.Serialization
{
    /// <summary>
    /// Represents a custom visitor or rewriter for expression trees.
    /// </summary>
    /// <remarks>
    /// initialize the translation of the entire expression tree
    /// </remarks>
    internal partial class ExpressionNodeBuilder: ExpressionVisitor
    {
        private ExpRepresentationBase _currentNode;
        private MemberBindingNodeBase _rootBindingNode;

        #region Build

        /// <summary>
        /// Transforms the specified expression into a ExpressionNodeBase.
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <param name="cache">The cache.</param>
        /// <returns></returns>
        public ExpRepresentationBase Build(Expression expression)
        {
            Contract.Requires(expression != null);

            Visit(expression);
            return _currentNode;
        }

        #endregion // Build

        #region IsQuery

        /// <summary>
        /// Determines whether the specified value is query.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="entityType">Type of the entity.</param>
        /// <returns>
        ///   <c>true</c> if the specified value is query; otherwise, <c>false</c>.
        /// </returns>
        //private static bool IsQuery(object value, out Type entityType)
        //{
        //    Type type = value.GetType();
        //    bool isQueryNode = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Query<>);
        //    if (isQueryNode)
        //    {
        //        entityType = type.GetGenericArguments()[0];
        //        return true;
        //    }
        //    entityType = null;
        //    return false;
        //}

        #endregion // IsQuery

        #region ThrowIfNotSupported

        /// <summary>
        /// Throws exception if not supported.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <exception cref="System.NotSupportedException">The specified node type is not supported:  + node.NodeType</exception>
        private void ThrowIfNotSupported(Expression node)
        {
            switch (node.NodeType)
            {
                case ExpressionType.AddAssign:
                case ExpressionType.AddAssignChecked:
                case ExpressionType.AndAssign:
                case ExpressionType.AddChecked:
                case ExpressionType.Assign:
                case ExpressionType.Block:
                case ExpressionType.DebugInfo:
                case ExpressionType.Default:
                case ExpressionType.DivideAssign:
                case ExpressionType.Dynamic:
                case ExpressionType.ExclusiveOrAssign:
                case ExpressionType.Extension:
                case ExpressionType.Goto:
                case ExpressionType.Index:
                case ExpressionType.Invoke:
                case ExpressionType.Label:
                case ExpressionType.LeftShiftAssign:
                case ExpressionType.Loop:
                case ExpressionType.ModuloAssign:
                case ExpressionType.MultiplyAssign:
                case ExpressionType.MultiplyAssignChecked:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.NegateChecked:
                case ExpressionType.NewArrayBounds:
                case ExpressionType.OrAssign:
                case ExpressionType.PostDecrementAssign:
                case ExpressionType.PostIncrementAssign:
                case ExpressionType.PowerAssign:
                case ExpressionType.PreDecrementAssign:
                case ExpressionType.PreIncrementAssign:
                case ExpressionType.RightShiftAssign:
                case ExpressionType.RuntimeVariables:
                case ExpressionType.SubtractAssign:
                case ExpressionType.SubtractAssignChecked:
                case ExpressionType.SubtractChecked:
                case ExpressionType.Switch:
                case ExpressionType.Throw:
                case ExpressionType.Try:
                case ExpressionType.Unbox:
                    throw new NotSupportedException("The specified node type is not supported: " + node.NodeType);
            }
        }

        #endregion // ThrowIfNotSupported

        #region GetInitializersNodes

        /// <summary>
        /// Gets the initializers nodes.
        /// </summary>
        /// <param name="initializers">The initializers.</param>
        /// <returns></returns>
        private ElementInitNode[] GetInitializersNodes(ReadOnlyCollection<ElementInit> initializers)
        {
            ElementInitNode[] initializersNodes = new ElementInitNode[initializers.Count];
            for (int index = 0; index < initializersNodes.Length; index++)
            {
                // Note: no support for more than one arguments.
                Visit(initializers[index].Arguments.First());
                initializersNodes[index] = new ElementInitNode
                {
                    MethodRepresentation = new SerializableMethodBase(initializers[index].AddMethod),
                    AddMethodName = initializers[index].AddMethod.Name,
                    Argument = _currentNode
                };
            }

            return initializersNodes;
        }

        #endregion // GetInitializersNodes

        #region Visitors (override)

        #region Visit

        /// <summary>
        /// Dispatches the expression to one of the more specialized visit methods in this class.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        public override Expression Visit(Expression node)
        {
            ThrowIfNotSupported(node);

            Expression newNode = base.Visit(node); // will call other virtual methods
            //_currentNode.Type = node.Type;
            return newNode;
        }

        #endregion // Visit

        #region VisitInvocation

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.InvocationExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitInvocation(InvocationExpression node)
        {
            Visit(node.Expression);
            var expression = _currentNode;
            ExpRepresentationBase[] args = new ExpRepresentationBase[node.Arguments.Count];
            for (int index = 0; index < args.Length; index++)
            {
                Visit(node.Arguments[index]);
                args[index] = _currentNode;
            }
            _currentNode = new InvocationNode
            {
                Arguments = args,
                TargetExpression = expression
            };

            return node;
        }

        #endregion // VisitInvocation

        #region VisitConditional

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.ConditionalExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitConditional(ConditionalExpression node)
        {
            Visit(node.Test);
            var test = _currentNode;
            Visit(node.IfTrue);
            var truePart = _currentNode;
            Visit(node.IfFalse);
            var falsePart = _currentNode;
            _currentNode = new ConditionalNode
            {
                Test = test,
                FalsePart = falsePart,
                TruePart = truePart
            };

            return node;
        }

        #endregion // VisitConditional

        #region VisitLambda

        /// <summary>
        /// Visits the lambda.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            Visit(node.Body);
            ExpRepresentationBase body = _currentNode;

            var parametersNodes = new List<ParameterNode>();
            foreach (var expParam in node.Parameters)
            {
                Visit(expParam);
                parametersNodes.Add((ParameterNode)_currentNode);
            }

            _currentNode = new LambdaNode
            {
                Body = body,
                Parameters = parametersNodes.ToArray()
                    //(from parameter in node.Parameters
                    // select VisitParameter(parameter)).ToArray()
                    // //select _cache.ParameterNodeToExpressionCache[parameter])
            };

            return node;
        }

        #endregion // VisitLambda

        #region VisitBinary

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.BinaryExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitBinary(BinaryExpression node)
        {
            Visit(node.Left);
            var leftNode = _currentNode;
            Visit(node.Right);
            var rightNode = _currentNode;
            _currentNode = new BinaryNode
            {
                Left = leftNode, // link to left child node
                Right = rightNode, // link to right child node
                Operator = node.NodeType
            };

            return node;
        }

        #endregion // VisitBinary

        #region VisitUnary

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.UnaryExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitUnary(UnaryExpression node)
        {
            Visit(node.Operand);
            var operand = _currentNode;

            var unaryNode = new UnaryNode
            {
                Operand = operand,
                Operator = node.NodeType,
            };
            if (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked)
            {
                unaryNode.TypeToConvertTo = new LightweightSerializableType(node.Type);
            }
            _currentNode = unaryNode;

            return node;
        }

        #endregion // VisitUnary

        #region VisitConstant

        /// <summary>
        /// Visits the <see cref="T:System.Linq.Expressions.ConstantExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitConstant(ConstantExpression node)
        {
            object value = node.Value;// != null && IsQuery(node.Value, out entityType) ? new ExpressionStub(entityType) : node.Value;
            _currentNode = new ConstantNode
            {
                Value = value
            };

            return node;
        }

        #endregion // VisitConstant

        #region VisitMethodCall

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MethodCallExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            ExpRepresentationBase[] args = new ExpRepresentationBase[node.Arguments.Count];
            for (int index = 0; index < args.Length; index++)
            {
                Visit(node.Arguments[index]);
                args[index] = _currentNode;
            }

            // instance
            if (node.Object != null)
            {
                // gets the instance of the method into _currentNode (e.g. i.ToString())
                Visit(node.Object);
            }
            else
            {
                // for instance object Visit(node.Object) will assign the _currentNode
                _currentNode = null;
            }

            _currentNode = new MethodCallNode
            {
                Instance = _currentNode, // instance
                Arguments = args, // link to argument child nodes
                MethodContextToken = new SerializableMethodBase(node.Method),
                MethodName = node.Method.Name
            };

            return node;
        }

        #endregion // VisitMethodCall

        #region VisitParameter

        /// <summary>
        /// Visits the <see cref="T:System.Linq.Expressions.ParameterExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitParameter(ParameterExpression node)
        {
            var parameterNode = new ParameterNode
            {
                Name = node.Name,
                TypeToken = new LightweightSerializableType(node.Type)
            };
        
            _currentNode = parameterNode;

            return node;
        }

        #endregion // VisitParameter

        #region VisitMember

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression != null)
            {
                Visit(node.Expression);
            }
            else
            {
                _currentNode = null;
            }
            _currentNode = new PropertyOrFieldNode
            {
                Target = _currentNode,
                MemberToken = new SerializableMemberInfo(node.Member),
                MemberName = node.Member.Name
            };

            return node;
        }

        #endregion // VisitMember

        #region VisitNew

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.NewExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitNew(NewExpression node)
        {
            ExpRepresentationBase[] args = new ExpRepresentationBase[node.Arguments.Count];
            for (int index = 0; index < args.Length; index++)
            {
                Visit(node.Arguments[index]);
                args[index] = _currentNode;
            }
            _currentNode = new NewInstanceNode
            {
                Arguments = args,
                ConstructorToken = new SerializableMethodBase(node.Constructor)
            };

            return node;
        }

        #endregion // VisitNew

        #region VisitMemberInit

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberInitExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitMemberInit(MemberInitExpression node)
        {
            Visit(node.NewExpression);
            NewInstanceNode newExpression = (NewInstanceNode)_currentNode;
            MemberBindingNodeBase[] bindings = new MemberBindingNodeBase[node.Bindings.Count];
            for (int index = 0; index < bindings.Length; index++)
            {
                VisitMemberBinding(node.Bindings[index]);
                bindings[index] = (MemberBindingNodeBase)_rootBindingNode;
            }

            _currentNode = new MemberInitNode
            {
                NewExpression = newExpression,
                Bindings = bindings
            };

            return node;
        }

        #endregion // VisitMemberInit

        #region VisitMemberBinding

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberBinding" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override MemberBinding VisitMemberBinding(MemberBinding node)
        {
            MemberBindingNodeBase binding = null;
            switch (node.BindingType)
            {
                case MemberBindingType.Assignment:
                    MemberAssignment assignment = (MemberAssignment)node;
                    Visit(assignment.Expression);
                    MemberAssignmentNode assignmentNode = new MemberAssignmentNode
                    {
                        AssignedExpression = _currentNode
                    };
                    binding = assignmentNode;
                    break;

                case MemberBindingType.ListBinding:
                    MemberListBinding listBinding = (MemberListBinding)node;
                    ElementInitNode[] initializers = GetInitializersNodes(listBinding.Initializers);
                    ListBindingNode listBindingNode = new ListBindingNode
                    {
                        Initializers = initializers
                    };
                    binding = listBindingNode;
                    break;

                case MemberBindingType.MemberBinding:
                    MemberMemberBinding recursiveBinding = (MemberMemberBinding)node;
                    MemberBindingNodeBase[] childrenBindings = new MemberBindingNodeBase[recursiveBinding.Bindings.Count];
                    for (int index = 0; index < childrenBindings.Length; index++)
                    {
                        VisitMemberBinding(recursiveBinding.Bindings[0]);
                        childrenBindings[index] = _rootBindingNode;
                    }
                    MemberMemberBindingNode memberBindingNode = new MemberMemberBindingNode
                    {
                        MemberBindings = childrenBindings
                    };
                    binding = memberBindingNode;
                    break;
            }

            binding.BindingType = node.BindingType;
            binding.MemberToken = new SerializableMemberInfo(node.Member);
            binding.MemberName = node.Member.Name;
            _rootBindingNode = binding;

            return node;
        }

        #endregion // VisitMemberBinding

        #region VisitNewArray

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.NewArrayExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitNewArray(NewArrayExpression node)
        {
            ExpRepresentationBase[] initializers = new ExpRepresentationBase[node.Expressions.Count];
            for (int index = 0; index < initializers.Length; index++)
            {
                Visit(node.Expressions[index]);
                initializers[index] = _currentNode;
            }
            _currentNode = new NewArrayInitNode
            {
                Initializers = initializers,
                TypeToken = new LightweightSerializableType(node.Type.GetElementType()) 
            };

            return node;
        }

        #endregion // VisitNewArray

        #region VisitListInit

        /// <summary>
        /// Visits the children of the <see cref="T:System.Linq.Expressions.ListInitExpression" />.
        /// </summary>
        /// <param name="node">The expression to visit.</param>
        /// <returns>
        /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
        /// </returns>
        protected override Expression VisitListInit(ListInitExpression node)
        {
            ElementInitNode[] initializers = GetInitializersNodes(node.Initializers);
            Visit(node.NewExpression);
            ListInitNode listInitNode = new ListInitNode
            {
                NewExpression = (NewInstanceNode)_currentNode,
                Initializers = initializers
            };
            _currentNode = listInitNode;

            return node;
        }

        #endregion // VisitListInit

        #endregion // Visitors (override)
    }
}